/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1998.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-2000 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s):
 * Norris Boyd
 *
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */

import java.io.*;

import org.mozilla.javascript.*;

import org.iwidget.model.javascript.IwidgetWidgetClass;

public class FileSupport
	extends IwidgetWidgetClass
{
	/**
	 * Some private data for this class.
	 */
	private String				name;
	private File				file;  // may be null, meaning to use System.out or .in
	private LineNumberReader	reader;
	private BufferedWriter		writer;


	public String getClassName() { return "File"; }
	
	public FileSupport()
	{
	}

	
	/**
	 * The Java method defining the JavaScript File constructor.
	 *
	 * If the constructor has one or more arguments, and the
	 * first argument is not undefined, the argument is converted
	 * to a string as used as the filename.<p>
	 *
	 * Otherwise System.in or System.out is assumed as appropriate
	 * to the use.
	*/
	public static Scriptable jsConstructor( Context cx, Object[] args,
											Function ctorObj, boolean inNewExpr)
	{
		FileSupport result = new FileSupport();
		if (args.length == 0 || args[0] == Context.getUndefinedValue())
		{
			result.name = "";
			result.file = null;
		}
		else
		{
			result.name = Context.toString(args[0]);
			result.file = new java.io.File(result.name);
		}
		return result;
	}


	/**
	 * Get the name of the file.
	 *
	 * Used to define the "name" property.
	 */
	public String jsGet_name() 
	{
		return name;
	}


	/**
	 * Read the remaining lines in the file and return them in an array.
	 *
	 * Implements a JavaScript function.<p>
	 *
	 * This is a good example of creating a new array and setting
	 * elements in that array.
	 *
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object
	 * @exception JavaScriptException if a JavaScript exception occurred
	 *            while creating the result array
	 */
	public Object jsFunction_readLines()
		throws IOException, JavaScriptException
	{
		StringBuffer sb = new StringBuffer();
		String s;
		while ((s = jsFunction_readLine()) != null)
		{
			sb.append( s ).append( "\n" );
		}

		Scriptable scope = ScriptableObject.getTopLevelScope(this);
		Scriptable result;
		try {
			String[] array = { sb.toString() };
			Context cx = Context.getCurrentContext();
			result = cx.newObject( scope, "Array", array );
		}
		catch (Exception e)
		{
			throw Context.reportRuntimeError(e.getMessage());
		}
		//catch (Exception e)
		//{
		//	throw Context.reportRuntimeError(e.getMessage());
		//}

		return result;
	}


	/**
	 * Read a line.
	 *
	 * Implements a JavaScript function.
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object, or EOFException if the object
	 *            reached the end of the file
	 */
	public String jsFunction_readLine() 
			throws IOException 
	{
		return getReader().readLine();
	}


	/**
	 * Read a character.
	 *
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object, or EOFException if the object
	 *            reached the end of the file
	*/
	public String jsFunction_readChar()
			throws IOException
	{
		int i = getReader().read();
		if (i == -1)
			return null;
		char[] charArray = { (char) i };
		return new String(charArray);
	}


	/**
	 * Write strings.
	 *
	 * Implements a JavaScript function. <p>
	 *
	 * This function takes a variable number of arguments, converts
	 * each argument to a string, and writes that string to the file.
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object
	 */
	public static void jsFunction_write(Context cx, Scriptable thisObj,
								Object[] args, Function funObj)
				throws IOException
	{
		write0(thisObj, args, false);
	}


	/**
	 * Write strings and a newline.
	 *
	 * Implements a JavaScript function.
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object
	 *
	 */
	public static void jsFunction_writeLine(Context cx, Scriptable thisObj,
					Object[] args, Function funObj)
			throws IOException
	{
		write0(thisObj, args, true);
	}


	/**
	 * Close the file. It may be reopened.
	 *
	 * Implements a JavaScript function.
	 * @exception IOException if an error occurred while accessing the file
	 *            associated with this object
	 */
	public void jsFunction_close()
				throws IOException
	{
		if (reader != null)
		{
			reader.close();
			reader = null;
		}
		else if (writer != null)
		{
			writer.close();
			writer = null;
		}
	}


	/**
	 * Finalizer.
	 *
	 * Close the file when this object is collected.
	 */
	public void finalize() 
	{
		try {
			jsFunction_close();
		}
		catch (IOException e)
		{
		}
	}


	/**
	 * Get the Java reader.
	 */
	public Object jsFunction_getReader()
	{
		if (reader == null)
			return null;

		// Here we use toObject() to "wrap" the BufferedReader object
		// in a Scriptable object so that it can be manipulated by
		// JavaScript.
		Scriptable parent = ScriptableObject.getTopLevelScope(this);
		return Context.toObject(reader, parent);
	}


	/**
	 * Get the Java writer.
	 *
	 * @see File#jsFunction_getReader
	 *
	 */
	public Object jsFunction_getWriter()
	{
		if (writer == null)
			return null;
		Scriptable parent = ScriptableObject.getTopLevelScope(this);
		return Context.toObject(writer, parent);
	}


	/**
	 * Get the reader, checking that we're not already writing this file.
	 */
	private LineNumberReader getReader()
		throws FileNotFoundException 
	{
		if (writer != null)
		{
			throw Context.reportRuntimeError("already writing file \"" + name + "\"");
		}
		if (reader == null)
			reader = new LineNumberReader(file == null
						? new InputStreamReader(System.in)
						: new FileReader(file));
		return reader;
	}


	/**
	 * Perform the guts of write and writeLine.
	 *
	 * Since the two functions differ only in whether they write a
	 * newline character, move the code into a common subroutine.
	 *
	 */
	private static void write0(Scriptable thisObj, Object[] args, boolean eol)
			throws IOException
	{
		FileSupport thisFile = checkInstance(thisObj);
		if (thisFile.reader != null)
		{
			throw Context.reportRuntimeError("already writing file \"" + thisFile.name + "\"");
		}
		if (thisFile.writer == null)
			thisFile.writer = new BufferedWriter(
					thisFile.file == null ? new OutputStreamWriter(System.out) : new FileWriter(thisFile.file));
		for (int i=0; i < args.length; i++)
		{
			String s = Context.toString(args[i]);
			thisFile.writer.write(s, 0, s.length());
		}
		if( eol )
			thisFile.writer.newLine();
	}

	/**
	 * Perform the instanceof check and return the downcasted File object.
	 *
	 * This is necessary since methods may reside in the File.prototype
	 * object and scripts can dynamically alter prototype chains. For example:
	 * <pre>
	 * js> defineClass("File");
	 * js> o = {};
	 * [object Object]
	 * js> o.__proto__ = File.prototype;
	 * [object File]
	 * js> o.write("hi");
	 * js: called on incompatible object
	 * </pre>
	 * The runtime will take care of such checks when non-static Java methods
	 * are defined as JavaScript functions.
	 */
	private static FileSupport checkInstance(Scriptable obj)
	{
		if (obj == null || !(obj instanceof File))
		{
			throw Context.reportRuntimeError("called on incompatible object");
		}
		return (FileSupport)obj;
	}
}
